#pragma once

#include "open62541.h"

#include <iostream>
#include <functional>
#include <map>
#include <thread>
#include <chrono>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <string>

#define ContinueRetry -1
#define MaxRetryCount 5

#define ReconnectTimeout 3000
#define DefaultTimeout 3000
#define RetryInterval 100

using OpcListenerHandler = std::function<void(const UA_NodeId &, const UA_Variant &)>;

struct OpcListenerItem
{
	UA_Variant value;
	UA_NodeId nodeId;
	OpcListenerHandler handler;
	UA_UInt32 monitoredItemId; // 重连前的ID，用于映射
	std::string nodeName;
};

class OpcListener
{
public:
	/**
	 * @brief OpcListener构造函数
	 *
	 * @details 初始化OPC UA客户端，设置默认配置和超时时间
	 */
	OpcListener();

	/**
	 * @brief OpcListener析构函数
	 *
	 * @details 负责清理OpcListener对象占用的资源，包括停止监听和释放OPC UA客户端资源
	 */
	~OpcListener();

	/**
	 * @brief 连接到指定的OPC终端节点
	 *
	 * @param[in] endpointUrl 终端节点URL地址
	 *
	 * @return 连接成功返回true，失败返回false
	 */
	bool connect(const std::string &endpointUrl);

	/**
	 * @brief 断开OPC监听器的连接
	 *
	 * 该函数负责断开OPC客户端的连接，清理订阅资源，并重置相关状态。
	 * 主要操作包括：设置连接状态为断开、删除订阅、断开客户端连接等。
	 */
	void disconnect();

	/**
	 * @brief 检查OPC监听器是否已连接
	 *
	 * @return bool 返回连接状态，true表示已连接，false表示未连接
	 */
	bool isConnected() const;

	/**
	 * @brief 检查OPC监听器是否正在运行
	 *
	 * @return bool 返回监听器的运行状态，true表示正在运行，false表示已停止
	 */
	bool isRunning() const;

	/**
	 * @brief 为指定节点添加监听器
	 *
	 * 将对象和成员函数绑定，作为指定节点的监听器添加到监听器列表中。
	 * 当节点发生变化时，绑定的成员函数将被调用。
	 *
	 * @param[in] nodeId 要监听的节点ID
	 * @param[in] obj 要绑定的对象指针
	 * @param[in] fun 要绑定的成员函数指针
	 *
	 * @return 添加成功返回true，否则返回false
	 */
	template <typename Obj, typename Fun>
	bool addListener(const UA_NodeId &nodeId, Obj *obj, Fun fun);

	/**
	 * @brief 为指定的OPC节点添加监听器
	 *
	 * 该函数用于为特定命名空间和节点名称的OPC节点注册监听回调函数。
	 * 通过绑定对象和成员函数的方式实现事件处理。
	 *
	 * @param[in] ns 节点所在的命名空间索引
	 * @param[in] nodeName 要监听的节点名称
	 * @param[in] obj 处理监听事件的对象指针
	 * @param[in] fun 处理监听事件的成员函数指针
	 *
	 * @return 添加监听器是否成功
	 * @retval true: 监听器添加成功
	 * @retval false: 监听器添加失败
	 */
	template <typename Obj, typename Fun>
	bool addListener(const int &ns, const std::string& nodeId, Obj *obj, Fun fun);

	/**
	 * @brief 为指定的OPC节点添加监听器
	 *
	 * 该函数用于为特定命名空间和节点ID的OPC节点注册监听回调函数。
	 * 通过绑定对象和成员函数来处理节点事件。
	 *
	 * @param ns 命名空间索引，引用传递
	 * @param nodeId 节点ID，引用传递
	 * @param obj 指向对象的指针，用于绑定成员函数
	 * @param fun 成员函数指针，指向具体的处理函数
	 *
	 * @return bool 添加监听器是否成功
	 *         - true: 监听器添加成功
	 *         - false: 监听器添加失败
	 */
	template <typename Obj, typename Fun>
	bool addListener(const int &ns, const int &nodeId, Obj *obj, Fun fun);

	/**
	 * @brief 为指定的OPC UA节点添加监听器，创建监视项并注册数据变更回调。
	 *
	 * 该函数会为给定的节点ID创建一个监视项，并设置数据变更回调函数。当节点数据发生变化时，
	 * 注册的回调函数会被触发。监视项信息会被存储在内部容器中以便后续管理。
	 *
	 * @param[in] nodeId 要监听的OPC UA节点的NodeId结构体引用。
	 * @param[in] handler 数据变更时调用的处理函数对象。
	 *
	 * @return 成功创建监视项并添加监听器返回true，否则返回false。
	 */
	bool addListener(const UA_NodeId &nodeId, OpcListenerHandler handler);

	/**
	 * @brief 从OPC监听器中移除指定节点的监听项
	 *
	 * 该函数会查找与指定节点ID关联的监听项，将其从监听器中移除，
	 * 并删除OPC UA客户端中的对应监视项。
	 *
	 * @param[in] nodeId 要移除监听的节点ID
	 *
	 * @return bool 操作成功返回true，失败返回false
	 */
	bool removeListener(const UA_NodeId &nodeId);

	/**
	 * @brief 停止OPC监听器
	 *
	 * 该函数用于停止OPC监听器的运行，包括停止工作线程、断开连接、
	 * 清理资源并通知等待的线程。确保所有相关资源得到正确释放。
	 */
	void stop();

	/**
	 * @brief 设置监听器的轮询间隔时间
	 *
	 * @param[in] intervalMs 轮询间隔时间，单位为毫秒
	 */
	void setIntervalMs(int intervalMs);

	/**
	 * @brief 设置重连参数
	 *
	 * @param[in] intervalMs 重连间隔时间，单位为毫秒
	 * @param[in] maxAttempts 最大重连尝试次数
	 */
	void setReconnectParameters(int intervalMs, int maxAttempts);

	template <typename Obj, typename Fun>
	OpcListenerHandler bindProcess(Obj*obj, Fun fun);

	/**
	 * @brief OPC UA客户端数据变化回调函数
	 *
	 * 当OPC UA服务器上的监控项数据发生变化时，此回调函数会被触发。
	 * 函数负责处理数据变化事件，更新本地缓存并执行相应的处理函数。
	 *
	 * @param[in] client OPC UA客户端实例指针
	 * @param[in] subId 订阅ID
	 * @param[in] subContext 订阅上下文指针，指向OpcListener实例
	 * @param[in] monId 监控项ID
	 * @param[in] monContext 监控项上下文指针
	 * @param[in] value 变化后的数据值
	 */
	static void DataChangeCallback(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, UA_DataValue *value);


private:
	/**
	 * @brief 尝试连接到 OPC UA 服务器并创建订阅，同时重新创建之前已订阅的监视项。
	 *
	 * 该函数首先尝试使用客户端连接到指定的 OPC UA 服务器端点。如果连接成功，
	 * 则创建一个新的订阅。随后，它会根据之前保存的监视项列表，重新创建这些监视项。
	 *
	 * @return bool 连接和订阅创建是否成功。
	 *              - true: 成功连接并创建订阅及监视项；
	 *              - false: 连接失败、订阅创建失败或监视项重建失败。
	 */
	bool internalConnect();

	/**
	 * @brief OPC监听器的工作线程函数
	 *
	 * 该函数在独立线程中运行，负责维护OPC连接状态、执行数据订阅更新以及处理重连逻辑。
	 * 工作流程如下：
	 * 1. 检查运行状态，若停止则退出线程
	 * 2. 若已连接，则执行订阅更新并休眠指定间隔
	 * 3. 若未连接，则根据重连配置尝试重连或停止客户端
	 *
	 * @note 该函数应在独立线程中调用，通过isRunning()控制线程生命周期
	 */
	void worker();

	/**
	 * @brief 根据订阅更新OPC客户端状态
	 *
	 * 该函数处理OPC客户端的网络事件和回调，检查连接状态。
	 * 如果连接断开，则执行相应的清理操作并为重连做准备。
	 */
	void updateBySubscription();

private:
	UA_Client *m_client = nullptr;							// OPC UA客户端实例指针
	std::string m_endpointUrl;								// OPC UA服务器端点URL
	UA_UInt32 m_subscriptionId = 0;							// 订阅ID
	int m_intervalMs = 100;									// 轮询间隔时间
	int m_counter = 0;										// 订阅计数器

	std::map<UA_UInt32, OpcListenerItem> m_listenerItems;	// 监听项列表
	std::mutex m_itemMutex;									// 保护 m_listenerItems 的访问

	std::atomic<bool> m_running = false;					// 线程运行状态
	std::atomic<bool> m_connected = false;					// opcua连接状态
	std::thread m_workerThread;								// 工作线程
	std::condition_variable m_cv;							// 条件变量
	std::mutex m_cvMutex;									// 保护 m_cv 的访问

	// 重连相关配置
	int m_currentReconnectAttempts = 0;						// 当前重连尝试次数
	int m_reconnectIntervalMs = ReconnectTimeout; 			// 重连间隔 (毫秒)
	int m_maxReconnectAttempts = ContinueRetry;   		    // 最大重连尝试次数 (-1 表示无限)
};

template<typename Obj, typename Fun>
inline bool OpcListener::addListener(const UA_NodeId & nodeId, Obj * obj, Fun fun)
{
	return addListener(nodeId, bindProcess(obj, fun));
}

template<typename Obj, typename Fun>
inline bool OpcListener::addListener(const int &ns, const std::string & nodeName, Obj * obj, Fun fun)
{
	return addListener(UA_NODEID_STRING(ns, (char*)nodeName.c_str()), bindProcess(obj, fun));
}

template<typename Obj, typename Fun>
inline bool OpcListener::addListener(const int &ns, const int &nodeId, Obj * obj, Fun fun)
{
	return addListener(UA_NODEID_NUMERIC(ns, nodeId), bindProcess(obj, fun));
}

template<typename Obj, typename Fun>
inline OpcListenerHandler OpcListener::bindProcess(Obj * obj, Fun fun)
{
	return std::bind(fun, obj, std::placeholders::_1, std::placeholders::_2);
}
